package ofxdownloader;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class is responsible for interpreting an OFX response sent back from
 * the server. It is used to extra useful information
 * @author Alex Spurling
 * @author Chris Thornton
 *
 */
public class OFXResponseParser {

	@SuppressWarnings("serial")
	public static class OFXParseException extends Exception {
		public OFXParseException(String message) {
			super(message);
		}
	}

	/**
	 * Gets the status code of the given response
	 * @param response
	 * @return
	 */
	public static String getStatusCode(OFXResponse response) {
		String statusBlock = getFirstTagContents(response.toString(), "STATUS");
		return getFirstTagContents(statusBlock, "CODE");
	}

	/**
	 * Gets the status code of the given response
	 * @param response
	 * @return
	 */
	public static String getStatusSeverity(OFXResponse response) {
		String statusBlock = getFirstTagContents(response.toString(), "STATUS");
		return getFirstTagContents(statusBlock, "SEVERITY");
	}

	/**
	 * Gets the status code of the given response
	 * @param response
	 * @return
	 */
	public static String getStatusMessage(OFXResponse response) {
		String statusBlock = getFirstTagContents(response.toString(), "STATUS");
		return getFirstTagContents(statusBlock, "MESSAGE");
	}

	/**
	 * Gets a list of Account objects from the given OFXResponse
	 * @param response
	 * @return
	 * @throws OFXParseException
	 */
	public static List<Account> getAccountList(OFXResponse response) throws OFXParseException {
		String accountListBlock = getFirstTagContents(response.toString(), "ACCTINFORS");
		if (accountListBlock == null) throw new OFXParseException("Could not find account list block");

		List<String> accountBlocks = getTagContents(accountListBlock, "ACCTINFO");
		if (accountBlocks.size() == 0) throw new OFXParseException("Cound not find account info block");

		List<Account> accountList = new ArrayList<Account>();
		//Loop through each account in the block
		for (String accountBlock: accountBlocks) {
			Account account = getAccount(accountBlock);
			accountList.add(account);
		}

		return accountList;
	}

	/**
	 * Finds the data relevant to an account in the given account block and
	 * returns it as a new Account object
	 * @param accountBlock
	 * @return
	 * @throws OFXParseException
	 */
	private static Account getAccount(String accountBlock) throws OFXParseException {

		//Check if it is a bank account we're dealing with
		if( getFirstTagContents(accountBlock, "BANKACCTFROM") != null )
		{
			String description = getFirstTagContents(accountBlock, "DESC");
			if (description == null) throw new OFXParseException("Account description not found in account block");
			String bankID = getFirstTagContents(accountBlock, "BANKID");
			if (bankID == null) throw new OFXParseException("Bank ID not found in account block");
			String branchID = getFirstTagContents(accountBlock, "BRANCHID");
			if (branchID == null) throw new OFXParseException("Branch ID not found in account block");
			String accID = getFirstTagContents(accountBlock, "ACCTID");
			if (accID == null) throw new OFXParseException("Account ID not found in account block");
			String accType = getFirstTagContents(accountBlock, "ACCTTYPE");
			if (accType == null) throw new OFXParseException("Account type not found in account block");
			return new BankAccount(description, bankID, branchID, accID, accType);
		}

		//or maybe a credit card instead
		else if( getFirstTagContents(accountBlock, "CCACCTFROM") != null )
		{
			String description = getFirstTagContents(accountBlock, "DESC");
			if (description == null) throw new OFXParseException("Account description not found in account block");
			String accID = getFirstTagContents(accountBlock, "ACCTID");
			if (accID == null) throw new OFXParseException("Account ID not found in account block");
			return new CCAccount(description, accID);
		}

		throw new OFXParseException("Unrecognised account type");

	}

	/**
	 * Returns the contents of the first tag found in the given search string
	 * @param toSearch
	 * @param tag
	 * @return
	 */
	private static String getFirstTagContents(String toSearch, String tag) {
		List<String> tags = getTagContents(toSearch, tag);
		if (tags.size() > 0) return tags.get(0);
		return null;
	}

	/**
	 * Returns a list of strings containing the contents of all the tags found
	 * in the given search string
	 * @param toSearch
	 * @param tag
	 * @return
	 */
	private static List<String> getTagContents(String toSearch, String tag) {
		List<String> blocks = new ArrayList<String>();

		//Find tags with closing tags
		Pattern p = Pattern.compile("<" + tag + ">\n(.*?)</" + tag + ">", Pattern.DOTALL);
		Matcher m = p.matcher(toSearch.toString());
		while (m.find()) {
			blocks.add(m.group(1));
		}
		//If we haven't found any open-tag/close-tag patterns
		if (blocks.size() == 0){
			//Find tags without closing tags
			p = Pattern.compile("^.*<" + tag + ">(.*)$", Pattern.MULTILINE);
			m = p.matcher(toSearch.toString());
			while (m.find()) {
				blocks.add(m.group(1));
			}
		}

		return blocks;
	}
}
